package environment;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.io.File;
import java.util.ArrayList;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.Timer;
import point.FixedMass;
import point.Mass;
import createobject.ForceFactory;
import createobject.ObjectFactory;
import force.ForceVector;
import line.Spring;



/**
 * Creates an component that is a viewer onto an animation.
 * @author Robert C. Duvall
 */
@SuppressWarnings("serial")
public class Canvas extends JComponent {
    /**
     * @param NO_KEY_PRESSED
     */
    public static final int NO_KEY_PRESSED = -1;
    /**
     * animate 25 times per second if possible (in milliseconds).
     */
    public static final int ONE_SECOND = 1000;
    /**
     * @param FRAMES_PER_SECOND
     */
    public static final int FRAMES_PER_SECOND = 25;
    /**
     * user's game to be animated.
     */
    private ArrayList<Simulation> myTarget = new ArrayList<Simulation>();
    /**
     * drives simulation.
     */
    private Timer myTimer;
    private int myLastKeyPressed;
    private Point myLastMousePosition;
    
    private static final JFileChooser OURCHOOSER = new JFileChooser(
            System.getProperties().getProperty("user.dir"));
    
    private ObjectFactory factory = new ObjectFactory();
    private ForceFactory forcefactory = new ForceFactory();

    /**
     * Initializes the canvas.
     * @param size size of canvas
     */
    public Canvas(final Dimension size) {
        // request component size
        setPreferredSize(size);
        // set component to receive user input
        setInputListeners();
        setFocusable(true);
        requestFocus();
        


        // create new blank simulation
        Simulation newSim = new Simulation(this);
        myTarget.add(newSim);
        loadModel(factory, newSim);
        
        // load forces onto canvas
        loadForces(forcefactory, newSim);

        // create timer to drive the animation
        myTimer = new Timer(ONE_SECOND / FRAMES_PER_SECOND,
                new ActionListener() {
            @Override
            public void actionPerformed(final ActionEvent e) {
                step((double) FRAMES_PER_SECOND / ONE_SECOND);
            }
        });
    }

    /**
     * Starts the applet's action, i.e., starts the animation.
     */
    public final void start() {
        myTimer.start();
    }

    /**
     * Take one step in the animation.
     * @param elapsedTime time elapsed
     */
    public final void step(final double elapsedTime) {
        for (Simulation s:myTarget) {
        s.update(elapsedTime);
        // indirectly causes paint to be called
        }
        repaint();
    }

    /**
     * Stops the applet's action, i.e., the animation.
     */
    public final void stop() {
        myTimer.stop();
    }

    /**
     * @return Returns the last key pressed by the player.
     *          (or -1 if none pressed).
     * @see java.awt.event.KeyEvent
     */
    public final int getLastKeyPressed() {
        return myLastKeyPressed;
    }

    /**
     * @return Returns the last position of the mouse in the canvas.
     */
    public final Point getLastMousePosition() {
        return myLastMousePosition;
    }

    /**
     * Paint the contents of the canvas.
     * 
     * Never called by you directly, instead called by Java runtime
     * when area of screen covered by this container needs to be
     * displayed (i.e., creation, uncovering, change in status)
     * 
     * @param pen used to paint shape on the screen
     */
    @Override
    public final void paintComponent(final Graphics pen) {
        pen.setColor(Color.WHITE);
        pen.fillRect(0, 0, getSize().width, getSize().height);
        for (Simulation s:myTarget) {
        s.paint((Graphics2D) pen);
        }
    }

    /**
     * Create listeners that will update state based on user input.
     */
    private void setInputListeners() {
        myLastKeyPressed = NO_KEY_PRESSED;
        addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(final KeyEvent e) {
                myLastKeyPressed = e.getKeyCode();
                manageSimulation(myLastKeyPressed);
            }

            @Override
            public void keyReleased(final KeyEvent e) {
                myLastKeyPressed = NO_KEY_PRESSED;
            }
        });
        myLastMousePosition = new Point();
        addMouseListener(new MouseAdapter() {

            @Override
            public void mousePressed(final MouseEvent e) {

                myTarget.get(0).add(new FixedMass(-1, e.getX(), e.getY(), 0));
                //find the nearest point by finding its ID
                int id = 0;
                //using for loop find the smallest distance in a simulation
                for (int l = 0; l < myTarget.size(); l++) {
                    for (int m = 0; m + 1
                           < myTarget.get(l).getObjectPoint().size(); m++) {
                        double distance = ForceVector.distanceBetween(e.getX()
                                - myTarget.get(l).getObjectPoint().
                                get(m).getCenter().getX(),
                                e.getY() - myTarget.get(l).getObjectPoint().
                                get(m).getCenter().getY());
                        if (distance > ForceVector.distanceBetween(
                                e.getX() - myTarget.get(l).getObjectPoint().
                                get(m + 1).getCenter().getX(),
                                e.getY() - myTarget.get(l).getObjectPoint().
                                get(m + 1).getCenter().getY())) {
                            distance = ForceVector.distanceBetween(e.getX()
                                   - myTarget.get(l).getObjectPoint().
                                    get(m + 1).getCenter().getX(),
                                    e.getY() - myTarget.get(l).
                                    getObjectPoint().get(m + 1).
                                    getCenter().getY());
                            //return the smallest distance and mass ID
                            id = myTarget.get(l).getObjectPoint().
                                    get(m + 1).getID();
                        }
                    }
                }
                /*add new spring to the spring ArrayList
                with constant rest length and k*/
                      myTarget.get(0).add(new Spring(myTarget.get(0).
                        getMass(id), myTarget.get(0).getMass(-1),
                        30.0, 0.003));
            }

            public void mouseReleased(final MouseEvent e) {
                //remove last ArrayList object when mouse released
                myTarget.get(0).getObjectPoint().
                remove(myTarget.get(0).getObjectPoint().size() - 1);
                myTarget.get(0).getLine().
                remove(myTarget.get(0).getLine().size() - 1);

            }
        });

        addMouseMotionListener(new MouseMotionAdapter() {
            @Override
            public void mouseDragged(final MouseEvent e) {
                myTarget.get(0).getObjectPoint().
                remove(myTarget.get(0).getObjectPoint().size() - 1);
                myTarget.get(0).getLine().
                remove(myTarget.get(0).getLine().size() - 1);
                int id = 0;
                for (int l = 0; l < myTarget.size(); l++) {
                    for (int m = 0; m + 1 < myTarget.get(l).
                            getObjectPoint().size(); m++) {
                        double distance = ForceVector.distanceBetween(e.getX()
                                - myTarget.get(l).getObjectPoint().
                                get(m).getCenter().getX(),
                                e.getY() - myTarget.get(l).getObjectPoint().
                                get(m).getCenter().getY());
                        if (distance > ForceVector.distanceBetween(e.getX()
                                - myTarget.get(l).getObjectPoint().get(m + 1).
                                getCenter().getX(),
                                e.getY() - myTarget.get(l).getObjectPoint().
                                get(m + 1).getCenter().getY())) {
                            distance = ForceVector.distanceBetween(e.getX()
                                    - myTarget.get(l).getObjectPoint().
                                    get(m + 1).getCenter().getX(),
                                    e.getY() - myTarget.get(l).getObjectPoint().
                                    get(m + 1).getCenter().getY());
                            id = myTarget.get(l).
                                    getObjectPoint().get(m + 1).getID();
                        }
                    }
                }
                myTarget.get(0).add(new FixedMass(-1, e.getX(), e.getY(), 0));
                myTarget.get(0).add(new Spring(myTarget.get(0).getMass(id),
                        myTarget.get(0).getMass(-1),
                        30.0,0.003
                        ));
                myLastMousePosition = e.getPoint();
            }
        });
    }

    /**
     * loads the factory.
     * @param f Factory
     */
    private void loadModel(ObjectFactory f, Simulation s) {
        int response = OURCHOOSER.showOpenDialog(null);
        if (response == JFileChooser.APPROVE_OPTION) {
           // for (Simulation s:myTarget) {
            f.loadModel(s, OURCHOOSER.getSelectedFile());
            //}
        }
    }
/**
 * 
 * @param f Load force factory
 */
    private void loadForces(ForceFactory f, Simulation s) {

        //int response = OURCHOOSER.showOpenDialog(null);
        //if (response == JFileChooser.APPROVE_OPTION) {
            //for (Simulation s:myTarget) {
            f.loadModel(s, new File("src/data/environment.xsp"));
            //}
       // }
    }
    /**
     * @param BOUNDSCHANGE 10
     */
    private static final int BOUNDSCHANGE = 10;

    /**
     * 
     * @param keyCode code for key press
     */
    private void manageSimulation(final int keyCode) {
        switch (keyCode) {
            case KeyEvent.VK_SPACE:
                if (myTimer.isRunning()) {
                    stop();
                }
                else {
                    start();
                }
                break;
            case KeyEvent.VK_S:
                step((double) FRAMES_PER_SECOND / ONE_SECOND);
                break;
            case KeyEvent.VK_P:
                System.out.println(myTarget);
                break;
            case KeyEvent.VK_G:
                    Mass.setgravityisRunning(!Mass.getgravityisRunning());
                break;
           case KeyEvent.VK_V:
                    Mass.setviscosityisrunning(!Mass.getviscosityisrunning());
                break;
           case KeyEvent.VK_M:
                   Mass.setcentermassisrunning(!Mass.getcentermassisrunning());
               break;
           case KeyEvent.VK_N:
               Simulation newSim = new Simulation(this);
               myTarget.add(newSim);
               loadModel(factory, newSim);
               loadForces(forcefactory, newSim);
               break;
           case KeyEvent.VK_C:
               myTarget.removeAll(myTarget);
               break;
           case KeyEvent.VK_UP:
               //change the walled area by 10
               Mass.setmyBoundschange(BOUNDSCHANGE);
               break;
           case KeyEvent.VK_DOWN:
             //change the walled area by 10
               Mass.setmyBoundschange(-BOUNDSCHANGE);
               break;
           case KeyEvent.VK_1:
                   Mass.setupwallrepulsionisrunning(
                           !Mass.getupwallrepulsionisrunning());
               break;
           case KeyEvent.VK_2:
                   Mass.setrightwallrepulsionisrunning(
                           !Mass.getrightwallrepulsionisrunning());
               break;
           case KeyEvent.VK_3:
               Mass.setbottomwallrepulsionisrunning(
                       !Mass.getbottomwallrepulsionisrunning());
               break;
           case KeyEvent.VK_4:
               Mass.setleftwallrepulsionisrunning(
                       !Mass.getleftwallrepulsionisrunning());
               break;
            default:
                // good style
                break;
        }
    }
}
